package org.onionframework.persistence.dao.projection;

import java.util.HashMap;
import java.util.Map;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.criterion.CriteriaQuery;
import org.hibernate.criterion.PropertyProjection;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.SessionFactoryImpl;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.Type;
import org.onionframework.core.Onion;
import org.onionframework.persistence.dao.functions.RowsConcatFunction;
import org.onionframework.persistence.dao.functions.rowsjoin.HSQLRowsConcatFunction;
import org.onionframework.persistence.dao.functions.rowsjoin.MYSQLRowsConcatFunction;
import org.onionframework.persistence.dao.functions.rowsjoin.OracleRowsConcatFunction;

public class HibernateRowsConcatProjection extends PropertyProjection {

	private static final long serialVersionUID = -2737361705325383217L;

	private static SessionFactoryImplementor factory = Onion.getBean(SessionFactoryImpl.class);

	private static Map<String, RowsConcatFunction> functions = new HashMap<String, RowsConcatFunction>();
	
	private RowsConcatFunction function;
	
	private String separator;

	static {
		functions.put("HSQL", new HSQLRowsConcatFunction());
		functions.put("MYSQL", new MYSQLRowsConcatFunction());
		functions.put("ORACLE", new OracleRowsConcatFunction());
	}

	public HibernateRowsConcatProjection(String property, String separator,
			boolean grouped) {
		super(property, grouped);
		this.separator = separator;
	}

	public static void registerFunction(String dialect,
			RowsConcatFunction function) {
		functions.put(dialect, function);
	}
	
	public Type[] getTypes(Criteria criteria, CriteriaQuery criteriaQuery)
			throws HibernateException {
		return new Type[] { StandardBasicTypes.STRING };
	}

	private String toSql(String column) {
		if (this.function == null) {
			String dialect = factory.getDialect().getClass().getSimpleName();
			for (Map.Entry<String, RowsConcatFunction> entry : functions
					.entrySet()) {
				if (dialect.toLowerCase().indexOf(entry.getKey().toLowerCase()) > -1) {
					this.function = entry.getValue();
					break;
				}
			}

			if (this.function == null)
				throw new RuntimeException(
						"RowsConcat projection is not supported for your current Dialect.");
		}

		return this.function.toSQL(column, this.separator);

	}

	public String toSqlString(Criteria criteria, int position,
			CriteriaQuery criteriaQuery) {

		StringBuilder sb = new StringBuilder();

		String[] cols = criteriaQuery.getColumns(super.getPropertyName(),
				criteria);
		if (cols.length > 1)
			throw new RuntimeException(
					"Hibernate RowsConcat Projection can only process one column.");

		sb.append(this.toSql(cols[0])).append(" as y").append(position + 0)
				.append('_');

		return sb.toString();
	}

	public String toGroupSqlString(Criteria criteria,
			CriteriaQuery criteriaQuery) throws HibernateException {

		String[] cols = criteriaQuery.getColumns(super.getPropertyName(),
				criteria);

		if (cols.length > 1)
			throw new RuntimeException(
					"Hibernate RowsJoin Projection can only process one column.");

		return this.toSql(cols[0]).toString();
	}

}
